package cc.yaomm.common.basic.captcha.handler;

import cc.yaomm.common.basic.captcha.generator.CaptchaGenerator;
import cc.yaomm.common.basic.domain.KeyValue;
import cc.yaomm.common.basic.enums.CaptchaType;
import cc.yaomm.common.basic.exception.BusinessException;
import cc.yaomm.common.basic.captcha.config.CaptchaProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Base64Utils;

import javax.imageio.ImageIO;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;

/**
 * Created by XQL on 2020/5/14.
 */
@Slf4j
public class ImageCaptchaHandler implements CaptchaHandler {

    private Random random = new Random();

    private String[] fontNames = {"宋体", "华文楷体", "黑体", "微软雅黑", "楷体_GB2312"};

    @Autowired
    CaptchaGenerator captchaGenerator;

    @Autowired
    CaptchaProperties captchaProperties;

    @Autowired
    StringRedisTemplate redisTemplate;

    public StringRedisTemplate getRedisTemplate() {
        return redisTemplate;
    }

    /**
     * <p>@Description : 生成图片验证码</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2020/5/20  </p>
     *
     * @param captchaType 验证码类型
     * @param payload     sessionID
     * @param params
     * @return
     */
    public String generate(CaptchaType captchaType, String payload, Object... params) {
        //获取验证码
        KeyValue<String, String> captcha = captchaGenerator.generate();
        String captchaBase64 = this.imageToBase64(captcha.getKey());

        //缓存验证 设置有效期
        this.setCache(captchaType, payload, captcha.getValue(), captchaProperties.getExpire());

        return captchaBase64;
    }

    /**
     * <p>@Description : 图片转base64</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2020/5/15  </p>
     */
    private String imageToBase64(String captcha) {
        BufferedImage bufferedImage = this.getImage(captcha, captchaProperties.getImageWight(), captchaProperties.getImageHeight());
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            ImageIO.write(bufferedImage, "JPEG", byteArrayOutputStream);
            byte[] imgBytes = Base64Utils.encode(byteArrayOutputStream.toByteArray());
            return "data:image/jpg;base64,".concat(new String(imgBytes));
        } catch (IOException e) {
            log.error("图片转base64异常", e);
            throw new BusinessException("生成图片验证码异常", e);
            //e.printStackTrace();
        }
    }

    // ！调用这个方法得到验证码
    private BufferedImage getImage(String captcha, int width, int height) {
        // 创建图片缓冲区
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 得到绘制环境
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 设置画布颜色
        Color color = this.getColor(200, 250);
        g2.setColor(color);
        g2.fillRect(0, 2, width, height - 4);

        // 添加干扰线
        this.drawLine(g2, width, height);
        // 画干扰点
        this.drawPoint(image, width, height);
        // x轴 扭曲
        this.shearX(g2, width, height, color);
        // y轴 扭曲
        this.shearY(g2, width, height, color);
        // 画验证码
        this.drawCaptcha(g2, width, height, captcha);
        return image;
    }

    // 画验证码
    private void drawCaptcha(Graphics2D g2, int width, int height, String captcha) {
        // 循环四次，每次生成一个字符
        for (int i = 0; i < captcha.length(); i++) {
            int fontSize = height - random.nextInt(12) - 4;
            g2.setColor(this.getColor(100, 160));
            g2.setFont(new Font("Algerian", random.nextInt(4), fontSize));
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * random.nextDouble() * (random.nextBoolean() ? 1 : -1),
                    (width / captcha.length()) * i + fontSize / 2, height / 2);
            g2.setTransform(affine);
            g2.drawChars(captcha.toCharArray(), i, 1, ((width - 10) / captcha.length()) * i + 5, height / 2 + fontSize / 2 - 10);
        }
    }

    // 生成随机的颜色
    private Color getColor(int min, int max) {
        min = min > 255 ? 255 : min;
        max = max > 255 ? 255 : max;
        int red = min + random.nextInt(max - min);
        int green = min + random.nextInt(max - min);
        int blue = min + random.nextInt(max - min);
        return new Color(red, green, blue);
    }

    // 画干扰线
    private void drawLine(Graphics2D g2, int width, int height) {
        g2.setColor(this.getColor(160, 200)); // 干扰线是蓝色
        for (int i = 0; i < 5; i++) {// 生成两个点的坐标，即4个值
            int x1 = random.nextInt(width - 1);
            int y1 = random.nextInt(height - 1);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            g2.setStroke(new BasicStroke(1.5F));
            g2.drawLine(x1, y1, x2, y2);
        }
    }

    // 画干扰点
    private void drawPoint(BufferedImage image, int width, int height) {
        float rate = 0.02f;// 噪声率
        int area = (int) (rate * width * height);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int rgb = this.getColor(0, 255).getRGB();
            image.setRGB(x, y, rgb);
        }
    }

    // x轴扭曲
    private void shearX(Graphics g2, int width, int height, Color color) {
        int period = random.nextInt(2);
        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);
        for (int i = 0; i < height; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
            g2.copyArea(0, i, width, 1, (int) d, 0);
            if (borderGap) {
                g2.setColor(color);
                g2.drawLine((int) d, i, 0, i);
                g2.drawLine((int) d + width, i, width, i);
            }
        }
    }

    // y轴扭曲
    private void shearY(Graphics g2, int width, int height, Color color) {
        int period = random.nextInt(40) + 10; // 50;
        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < width; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
            g2.copyArea(i, 0, 1, height, 0, (int) d);
            if (borderGap) {
                g2.setColor(color);
                g2.drawLine(i, (int) d, i, 0);
                g2.drawLine(i, (int) d + height, i, height);
            }
        }
    }
}